package com.xmzs.web.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.xmzs.common.chat.config.LocalCache;
import com.xmzs.common.chat.domain.request.ChatRequest;
import com.xmzs.common.chat.entity.chat.*;
import com.xmzs.common.chat.entity.images.Image;
import com.xmzs.common.chat.entity.images.ImageResponse;
import com.xmzs.common.chat.entity.images.SizeEnum;
import com.xmzs.common.chat.listener.SSEEventSourceListener;
import com.xmzs.common.chat.openai.OpenAiClient;
import com.xmzs.common.chat.openai.OpenAiStreamClient;
import com.xmzs.common.chat.utils.TikTokensUtil;
import com.xmzs.common.core.domain.model.LoginUser;
import com.xmzs.common.core.exception.ServiceException;
import com.xmzs.common.core.exception.base.BaseException;
import com.xmzs.common.oss.core.OssClient;
import com.xmzs.common.oss.entity.UploadResult;
import com.xmzs.common.oss.factory.OssFactory;
import com.xmzs.common.satoken.utils.LoginHelper;
import com.xmzs.system.domain.vo.SysUserVo;
import com.xmzs.system.mapper.SysUserMapper;
import com.xmzs.system.service.ISysOssService;
import com.xmzs.system.service.ISysUserService;
import com.xmzs.web.service.ChatService;
import com.xmzs.web.service.SseService;
import jakarta.xml.bind.DatatypeConverter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;

/**
 * 描述：
 *
 * @author https:www.unfbx.com
 * @date 2023-04-08
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class SseServiceImpl implements SseService {

    private final OpenAiStreamClient openAiStreamClient;

    private final ISysUserService sysUserService;

    private final ChatService chatService;

    private final ISysOssService ossService;



    @Override
    public ResponseBodyEmitter sseChat(ChatRequest chatRequest) {
        LoginUser loginUser = LoginHelper.getLoginUser();
        if(loginUser ==null){
            throw new ServiceException("用户未登录！");
        }
        if (StrUtil.isBlank(chatRequest.getPrompt())) {
            throw new BaseException("参数异常，msg不能为空~");
        }
        SysUserVo sysUserVo = sysUserService.selectUserById(loginUser.getUserId());
        if(sysUserVo!=null && sysUserVo.getTokens()<1){
            throw new ServiceException("余额不足,请联系管理员充值！");
        }

        ResponseBodyEmitter sseEmitter = getResponseBodyEmitter(chatRequest);
        SSEEventSourceListener openAIEventSourceListener = new SSEEventSourceListener(sseEmitter);

        // 上下文信息
        List<Content> contentList = new ArrayList<>();

        LinkedList<Message> messages = new LinkedList<>();

        if(Image.Model.DALL_E_3.getName().equals(chatRequest.getModel())){
            Image image = Image.builder()
                .responseFormat(com.xmzs.common.chat.entity.images.ResponseFormat.B64_JSON.getName())
                .model(Image.Model.DALL_E_3.getName())
                .prompt(chatRequest.getPrompt())
                .n(1)
                .quality(Image.Quality.STANDARD.getName())
                .size(SizeEnum.size_1024.getName())
                .style(Image.Style.VIVID.getName())
                .build();
            ImageResponse imageResponse = openAiStreamClient.genImages(image);
            String urlB64 = imageResponse.getData().get(0).getB64Json();

            String imageUrl=compressPicForScale(urlB64, 256);
            byte[] decode= DatatypeConverter.parseBase64Binary(imageUrl);
            OssClient storage = OssFactory.instance();
            UploadResult upload=storage.upload(decode, storage.getPath("panda",".png"), "image/png");
            try {
                sseEmitter.send("<img src='"+upload.getUrl()+"' width=300 height=260>");
                sseEmitter.complete();
                log.info("图片生成成功!{}",upload.getUrl());
            }catch (Exception e){
                log.info("图片生成失败!{}",e.getMessage());
            }
            return sseEmitter;
        }

        // 图文识别模型
        if(ChatCompletion.Model.GPT_4_VISION_PREVIEW.getName().equals(chatRequest.getModel())) {
            Content textContent = Content.builder().text(chatRequest.getPrompt()).type(Content.Type.TEXT.getName()).build();
            ImageUrl imageUrl = ImageUrl.builder().url(chatRequest.getImgurl()).build();
            Content imageContent = Content.builder().imageUrl(imageUrl).type(Content.Type.IMAGE_URL.getName()).build();
            contentList.add(textContent);
            contentList.add(imageContent);
            MessagePicture message = MessagePicture.builder().role(Message.Role.USER.getName()).content(contentList).build();
            ChatCompletionWithPicture chatCompletion = ChatCompletionWithPicture
                .builder()
                .messages(Collections.singletonList(message))
                .model(chatRequest.getModel())
                .user(chatRequest.getConversationId())
                .temperature(chatRequest.getTemperature())
                .topP(chatRequest.getTop_p())
                .stream(true)
                .build();
            openAiStreamClient.streamChatCompletion(chatCompletion, openAIEventSourceListener);
        }else {
            // 判断是否需要携带上下文信息
            if(chatRequest.getUsingContext()){
                // 获取对话记录
                messages = LocalCache.getUserChatMessages(chatRequest.getConversationId(), chatRequest.getContentNumber());
            }
            // 设置系统角色
            Message systemMessage = Message.builder().content(chatRequest.getSystemMessage()).role(Message.Role.SYSTEM).build();
            messages.addFirst(systemMessage);
            // 添加本次消息记录
            Message message = Message.builder().content(chatRequest.getPrompt()).role(Message.Role.USER).build();
            messages.add(message);
            ChatCompletion completion = ChatCompletion
                .builder()
                .messages(messages)
                .model(chatRequest.getModel())
                .user(chatRequest.getConversationId())
                .temperature(chatRequest.getTemperature())
                .topP(chatRequest.getTop_p())
                .stream(true)
                .build();
            openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
            LocalCache.MESSAGE.put(chatRequest.getConversationId(), messages);
        }
        if (ChatCompletion.Model.GPT_4_1106_PREVIEW.getName().equals(chatRequest.getModel())) {
            // 扣除tokens
            int tokens = TikTokensUtil.tokens(BaseChatCompletion.Model.GPT_4_0613.getName(),  new ArrayList<>(messages)) / 2;
            chatService.deductToken(tokens);
        }

        return sseEmitter;
    }


    /**
     * 创建sseEmitter
     * @param chatRequest
     * @return
     */
    private static ResponseBodyEmitter getResponseBodyEmitter(ChatRequest chatRequest) {

        ResponseBodyEmitter sseEmitter = new ResponseBodyEmitter(0L);

        sseEmitter.onCompletion(() -> {
            log.info("[{}]结束连接...................", chatRequest.getConversationId());
            LocalCache.CACHE.remove(chatRequest.getConversationId());
        });

        //超时回调
        sseEmitter.onTimeout(() -> {
            log.error("[{}]连接超时...................", chatRequest.getConversationId());
        });

        //异常回调
        sseEmitter.onError(
            throwable -> {
                log.error("[{}]连接失败...................", chatRequest.getConversationId());
            }
        );

        LocalCache.CACHE.put(chatRequest.getConversationId(), sseEmitter);

        log.info("[{}]创建sse连接成功！", chatRequest.getConversationId());

        return sseEmitter;
    }

    /**
     * 根据指定大小压缩图片,并转成base64
     *
     * @param image  base64图片
     * @param desFileSize 指定图片大小，单位kb
     * @return 压缩质量后的图片字节数组
     */
    public static String compressPicForScale(String image, long desFileSize) {
        byte[] imageBytes = org.apache.commons.codec.binary.Base64.decodeBase64(image);
        if (imageBytes == null || imageBytes.length <= 0 || imageBytes.length < desFileSize * 1024) {
            return org.apache.commons.codec.binary.Base64.encodeBase64String(imageBytes);
        }
        long srcSize = imageBytes.length;
        double accuracy = getAccuracy(srcSize / 1024);
        try {
            while (imageBytes.length > desFileSize * 1024) {
                ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream(imageBytes.length);
                Thumbnails.of(inputStream)
                    .scale(accuracy)
                    .outputQuality(accuracy)
                    .toOutputStream(outputStream);
                imageBytes = outputStream.toByteArray();
            }
            log.info("【图片压缩】图片原大小={}kb | 压缩后大小={}kb", srcSize / 1024, imageBytes.length / 1024);
        } catch (Exception e) {
            log.error("【图片压缩】msg=图片压缩失败!", e);
        }

        return Base64.encodeBase64String(imageBytes);
    }


    /**
     * 自动调节精度(经验数值)
     *
     * @param size 源图片大小
     * @return 图片压缩质量比
     */
    public static double getAccuracy(long size) {
        double accuracy;
        if (size < 900) {
            accuracy = 0.85;
        } else if (size < 2047) {
            accuracy = 0.6;
        } else if (size < 3275) {
            accuracy = 0.44;
        } else {
            accuracy = 0.4;
        }
        return accuracy;
    }

}
